package com.sagframe.sagacity.sqltoy.plus.multi;

import com.sagframe.sagacity.sqltoy.plus.*;
import com.sagframe.sagacity.sqltoy.plus.conditions.ISqlSegment;
import com.sagframe.sagacity.sqltoy.plus.conditions.eumn.CompareEnum;
import com.sagframe.sagacity.sqltoy.plus.conditions.eumn.SqlKeyword;
import com.sagframe.sagacity.sqltoy.plus.conditions.interfaces.SFunction;
import com.sagframe.sagacity.sqltoy.plus.conditions.segments.MatchSegment;
import com.sagframe.sagacity.sqltoy.plus.conditions.toolkit.LambdaUtils;
import com.sagframe.sagacity.sqltoy.plus.conditions.toolkit.FiledNature;
import com.sagframe.sagacity.sqltoy.plus.conditions.toolkit.StringPool;
import com.sagframe.sagacity.sqltoy.plus.multi.model.LambdaColumn;
import com.sagframe.sagacity.sqltoy.plus.multi.model.StrColumn;
import org.sagacity.sqltoy.exception.DataAccessException;
import org.sagacity.sqltoy.utils.StringUtil;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class MultiContext implements MultiISqlAssembler, ISqlSegment{

    protected AtomicInteger alisaNameSeq;

    protected AtomicInteger paramNameSeq;

    private Class<?> fromClass;

    protected Class<?> toClass;

    private final List<Class<?>> entityClasses;

    private final Map<Class<?>, String> alisaNameMap ;

    private final Map<String, Class<?>> alisaNameClassMap;

    private final Map<Class<?>, String[]> entityClassFieldsMap;

    private ISqlSegment operateSegment;

    private final List<ISqlSegment> conditionSqlSegments;

    private List<ISqlSegment> updateSqlSegments;

    private List<ISqlSegment> selectSqlSegments;

    private List<ISqlSegment> tableSqlSegments;

    private List<ISqlSegment> deleteTableSqlSegments;

    private final MultiISqlAssemble sqlAssemble;

    private MultiFiledMappingStrategy mappingStrategy;

    public MultiContext(Class<?> toClass, Map<Class<?>, String> alisaNameMap, List<ISqlSegment> conditionSqlSegments) {
        this.toClass = toClass;
        this.alisaNameSeq = new AtomicInteger(0);
        this.paramNameSeq = new AtomicInteger(0);
        this.entityClasses = new ArrayList<>();
        this.sqlAssemble = new MultiISqlAssemble();
        this.conditionSqlSegments = conditionSqlSegments;
        this.alisaNameMap = alisaNameMap;
        this.alisaNameClassMap = new HashMap<>();
        this.entityClassFieldsMap = new HashMap<>();
    }

    public void addSelectSqlSegment(ISqlSegment sqlSegment) {
        if (selectSqlSegments == null) {
            selectSqlSegments = new ArrayList<>();
        }
        selectSqlSegments.add(sqlSegment);
    }

    public void addUpdateSqlSegment(ISqlSegment sqlSegment) {
        if (updateSqlSegments == null) {
            updateSqlSegments = new ArrayList<>();
        }
        updateSqlSegments.add(sqlSegment);
    }

    public void addTableSqlSegment(ISqlSegment sqlSegment) {
        if (tableSqlSegments == null) {
            tableSqlSegments = new ArrayList<>();
        }
        tableSqlSegments.add(sqlSegment);
    }

    public void addDeleteSqlSegment(ISqlSegment sqlSegment) {
        if (deleteTableSqlSegments == null) {
            deleteTableSqlSegments = new ArrayList<>();
        }
        deleteTableSqlSegments.add(sqlSegment);
    }

    public void addSelectSqlAssembler(LambdaColumn... lambdaColumns) {
        if (lambdaColumns != null && lambdaColumns.length > 0) {
            for (LambdaColumn lambdaColumn : lambdaColumns) {
                this.addSqlAssembler(mappingStrategy -> {
                    this.addSelectSqlSegment((ISqlSegment) () -> getSelectColumn(mappingStrategy, lambdaColumn));
                });
            }
        }
    }

    public void addSelectSqlAssembler(Class<?> mappingClass, LambdaColumn... lambdaColumns) {
        if (mappingClass != null) {
            this.addSqlAssembler(mappingStrategy -> {
                this.addSelectSqlSegment((ISqlSegment) () -> {
                    if (!entityClasses.contains(mappingClass)) {
                        return null;
                    }
                    String[] mappingFieldNames = entityClassFieldsMap.computeIfAbsent(mappingClass, mappingStrategy::getEntityFields);
                    if (mappingFieldNames == null || mappingFieldNames.length <= 0) {
                        return null;
                    }
                    return Arrays.stream(mappingFieldNames).map(e -> mappingStrategy.getColumnName(MultiContext.this, mappingClass, e) + " AS " + e).collect(Collectors.joining(", "));
                });
            });
        }
        if (lambdaColumns != null && lambdaColumns.length > 0) {
            for (LambdaColumn lambdaColumn : lambdaColumns) {
                this.addSqlAssembler(mappingStrategy -> {
                    this.addSelectSqlSegment((ISqlSegment) () -> getSelectColumn(mappingStrategy, lambdaColumn));
                });
            }
        }
    }

    public void addSelectSqlAssembler(StrColumn... strColumns) {
        if (strColumns != null && strColumns.length > 0) {
            for (StrColumn column : strColumns) {
                this.addSqlAssembler(mappingStrategy -> {
                    this.addSelectSqlSegment((ISqlSegment) () -> getSelectColumn(mappingStrategy, column));
                });
            }
        }
    }

    public void addSelectSqlAssembler(Class<?> mappingClass, StrColumn... strColumns) {
        if (mappingClass != null) {
            this.addSelectSqlSegment((ISqlSegment) () -> {
                if (!entityClasses.contains(mappingClass)) {
                    return null;
                }
                String[] mappingFieldNames = entityClassFieldsMap.computeIfAbsent(mappingClass, mappingStrategy::getEntityFields);
                if (mappingFieldNames == null || mappingFieldNames.length <= 0) {
                    return null;
                }
                return Arrays.stream(mappingFieldNames).map(e -> mappingStrategy.getColumnName(MultiContext.this, mappingClass, e) + " AS " + e).collect(Collectors.joining(", "));
            });
        }
        if (strColumns != null && strColumns.length > 0) {
            for (StrColumn column : strColumns) {
                this.addSqlAssembler(mappingStrategy -> {
                    this.addSelectSqlSegment((ISqlSegment) () -> getSelectColumn(mappingStrategy, column));
                });
            }
        }
    }

    public <T> void addUpdateSqlAssembler(SFunction<T, ?> column, Object val) {
        this.addSqlAssembler(mappingStrategy -> {
            FiledNature nature = LambdaUtils.extractToNature(column);
            String paramName = getParamName(nature.getFiledName());
            this.addUpdateSqlSegment(new ISqlSegment() {
                @Override
                public String getSqlSegment() {
                    String columnName = mappingStrategy.getColumnName(MultiContext.this, nature.getClazz(), nature.getFiledName());
                    return CompareEnum.EQ.getMetaSql(columnName, paramName);
                }

                @Override
                public Map<String, Object> getSqlSegmentParamMap() {
                    HashMap<String, Object> map = new HashMap<>();
                    map.put(paramName, val);
                    return map;
                }
            });
        });
    }

    public <T> void addUpdateSqlAssembler(String column, Object val) {
        this.addSqlAssembler(mappingStrategy -> {
            FiledNature nature = getFiledNature(column);
            String paramName = getParamName(nature.getFiledName());
            this.addUpdateSqlSegment(new ISqlSegment() {
                @Override
                public String getSqlSegment() {
                    String columnName = mappingStrategy.getColumnName(MultiContext.this, nature.getClazz(), nature.getFiledName());
                    return CompareEnum.EQ.getMetaSql(columnName, paramName);
                }

                @Override
                public Map<String, Object> getSqlSegmentParamMap() {
                    HashMap<String, Object> map = new HashMap<>();
                    map.put(paramName, val);
                    return map;
                }
            });
        });
    }

    public void addOperateSqlSegment(ISqlSegment sqlSegment) {
        this.operateSegment = sqlSegment;
    }

    public void addConditionSqlSegment(ISqlSegment sqlSegment) {
        conditionSqlSegments.add(sqlSegment);
    }

    public void addEntityClass(Class<?> clazz) {
        if (entityClasses.contains(clazz)) {
            throw new DataAccessException("match table query Self-association is not supported");
        }
        entityClasses.add(clazz);
    }

    public void putTableAlisaName(Class<?> clazz, String alisaName) {
        if (alisaNameMap.get(clazz) != null) {
            return;
        }
        if (StringUtil.isBlank(alisaName)) {
            alisaName = genAlisaName(clazz);
        }
        alisaNameMap.put(clazz, alisaName);
        alisaNameClassMap.put(alisaName, clazz);
    }

    public String getEntityAlisaName(Class<?> clazz) {
        return alisaNameMap.get(clazz);
    }

    protected String genAlisaName(Class<?> clazz) {
        return clazz.getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT) + alisaNameSeq.incrementAndGet();
    }

    /**
     * sql组装器
     * @param assembler
     */
    public void addSqlAssembler(MultiISqlAssembler assembler) {
        sqlAssemble.add(assembler);
    }

    public void addTableSqlAssembler(ISqlSegment sqlSegment) {
        addSqlAssembler((strategy) -> addTableSqlSegment(sqlSegment));
    }

    public void addTableSqlAssembler(Class<?> clazz) {
        addSqlAssembler(mappingStrategy -> this.addTableSqlSegment(() -> mappingStrategy.getEntityName(clazz) + StringPool.SPACE + this.getEntityAlisaName(clazz)));
    }

    public String getParamName(String entityFiledName) {
        return entityFiledName + StringPool.WRAPPER_PARAM_MIDDLE + StringPool.WRAPPER_PARAM + paramNameSeq.incrementAndGet();
    }

    public void addDeleteSqlAssembler(Class<?> clazz) {
        addSqlAssembler(mappingStrategy -> {
            validateEntityClass(clazz);
            this.addDeleteSqlSegment(() -> getEntityAlisaName(clazz));
        });
    }

    public void setFromClass(Class<?> fromClass) {
        this.fromClass = fromClass;
    }

    private void validateEntityClass(Class<?> clazz) {
        if (!entityClasses.contains(clazz)) {
            throw new DataAccessException("The table in sql does not exist");
        }
    }

    /**
     * SQL 片段
     */
    @Override
    public String getSqlSegment() {
        StringBuilder sqlSd = new StringBuilder();
        String conditionSql = getConditionSql();
        if (MatchSegment.SELECT.match(operateSegment)) {
            sqlSd.append(operateSegment.getSqlSegment())
                    .append(StringPool.SPACE).append(getSelectColumnSql())
                    .append(StringPool.SPACE).append(SqlKeyword.FROM.getSqlSegment())
                    .append(StringPool.SPACE).append(getTableLinkSql());
        } else if (MatchSegment.UPDATE.match(operateSegment)) {
            sqlSd.append(operateSegment.getSqlSegment())
                    .append(StringPool.SPACE).append(getTableLinkSql())
                    .append(StringPool.SPACE).append(SqlKeyword.SET.getSqlSegment())
                    .append(StringPool.SPACE).append(getUpdateColumnSql());
        } else if (MatchSegment.DELETE.match(operateSegment)) {
            sqlSd.append(operateSegment.getSqlSegment())
                    .append(StringPool.SPACE).append(getDeleteTableSql())
                    .append(StringPool.SPACE).append(SqlKeyword.FROM.getSqlSegment())
                    .append(StringPool.SPACE).append(getTableLinkSql());
        }
        //拼装sql条件语句
        if (StringUtil.isNotBlank(conditionSql)) {
            sqlSd.append(StringPool.SPACE).append(SqlKeyword.WHERE.getSqlSegment())
                    .append(StringPool.SPACE).append(conditionSql);
        }
        return sqlSd.toString();
    }

    @Override
    public Map<String, Object> getSqlSegmentParamMap() {
        Map<String, Object> segmentParamMap = new HashMap<>();
        if (MatchSegment.UPDATE.match(operateSegment)) {
            if (updateSqlSegments != null && !updateSqlSegments.isEmpty()) {
                segmentParamMap.putAll(updateSqlSegments.stream().map(ISqlSegment::getSqlSegmentParamMap).filter(Objects::nonNull).flatMap(x -> x.entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (p1, p2) -> p1)));
            }
        }
        if (conditionSqlSegments != null && !conditionSqlSegments.isEmpty()) {
            segmentParamMap.putAll(conditionSqlSegments.stream().map(ISqlSegment::getSqlSegmentParamMap).filter(Objects::nonNull).flatMap(x -> x.entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (p1, p2) -> p1)));
        }
        return segmentParamMap;
    }

    /**
     * 组装
     *
     * @param mappingStrategy -字段映射策略
     */
    @Override
    public void assemble(MultiFiledMappingStrategy mappingStrategy) {
        this.mappingStrategy = mappingStrategy;
        this.sqlAssemble.assemble(mappingStrategy);
    }

    private String getSelectColumn(MultiFiledMappingStrategy mappingStrategy, LambdaColumn column) {
        FiledNature nature = LambdaUtils.extractToNature(column.getColumn());
        String columnName = mappingStrategy.getColumnName(MultiContext.this, nature.getClazz(), nature.getFiledName());
        return column.getSqlFunctionEnum() != null ? column.getSqlFunctionEnum().getSqlSegment(columnName) + column.getAsName() : columnName + column.getAsName();
    }

    private String getSelectColumn(MultiFiledMappingStrategy mappingStrategy, StrColumn column) {
        FiledNature nature = getFiledNature(column.getColumn());
        String columnName = mappingStrategy.getColumnName(MultiContext.this, nature.getClazz(), nature.getFiledName());
        return column.getSqlFunctionEnum() != null ? column.getSqlFunctionEnum().getSqlSegment(columnName) + column.getAsName() : columnName + column.getAsName();
    }

    /**
     * string类型 a1.filedName
     * @param column
     * @return
     */
    public FiledNature getFiledNature(String column) {
        String[] alisaAndFiledNames = column.trim().split("\\.");
        if (alisaAndFiledNames.length != 2) {
            throw new DataAccessException("filedName:" + column + "multi wrapper param error!");
        }
        String alisaName = alisaAndFiledNames[0];
        return new FiledNature(alisaAndFiledNames[1], alisaNameClassMap.get(alisaName));
    }

    private String getTableLinkSql() {
        return tableSqlSegments.stream().map(ISqlSegment::getSqlSegment).collect(Collectors.joining(" "));
    }

    public String getSelectColumnSql() {
        if (selectSqlSegments == null || selectSqlSegments.isEmpty()) {
            return getSelectEntityClassColumnSql();
        } else {
            String selectSql = selectSqlSegments.stream().map(ISqlSegment::getSqlSegment).filter(Objects::nonNull).collect(Collectors.joining(", "));
            if (selectSql.isEmpty()) {
                return getSelectEntityClassColumnSql();
            }
            return selectSql;
        }
    }

    public String getSelectEntityClassColumnSql() {
        String[] toFieldNames = entityClassFieldsMap.computeIfAbsent(toClass, mappingStrategy::getEntityFields);
        if (toClass != null && entityClasses.contains(toClass) && toFieldNames != null && toFieldNames.length > 0) {
            return Arrays.stream(toFieldNames).map(e -> mappingStrategy.getColumnName(MultiContext.this, toClass, e) + " AS " + e).collect(Collectors.joining(", "));
        } else {
            if (toClass != null) {
                return StringPool.ASTERISK;
            }
            //没有指定结果集就默认fromClass作为结果查询
            String[] fromFieldNames = entityClassFieldsMap.get(fromClass);
            if (fromFieldNames != null && fromFieldNames.length > 0) {
                return Arrays.stream(fromFieldNames).map(e -> mappingStrategy.getColumnName(MultiContext.this, toClass, e) + " AS " + e).collect(Collectors.joining(", "));
            } else {
                throw new DataAccessException("from class multi wrapper param error has no columns!");
            }
        }
    }

    public String getUpdateColumnSql() {
        if (updateSqlSegments == null || updateSqlSegments.isEmpty()) {
            return StringPool.EMPTY;
        }
        return updateSqlSegments.stream().map(ISqlSegment::getSqlSegment).collect(Collectors.joining(", "));
    }

    public String getConditionSql() {
        if (conditionSqlSegments == null || conditionSqlSegments.isEmpty()) {
            return "1 = 1";
        }
        return conditionSqlSegments.stream().map(ISqlSegment::getSqlSegment).collect(Collectors.joining(" "));
    }

    public String getDeleteTableSql() {
        if (deleteTableSqlSegments == null || deleteTableSqlSegments.isEmpty()) {
            //未设置删除的表默认取from表
            return getEntityAlisaName(fromClass);
        } else {
            return deleteTableSqlSegments.stream().map(ISqlSegment::getSqlSegment).collect(Collectors.joining(", "));
        }
    }

}
